﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Weazel.Willows.Model
{
	public partial class Module 
		: HierarchicalEntity
	{
		/// <summary>
		/// Value indicating if this module has been refined (or not)
		/// </summary>
		private bool refined = false;

		/// <summary>
		/// This is a list of the names of reader interfaces that HAS been 
		/// refined. Once a reader interface has been refined it will add its
		/// name to this list. Other reader interfaces can use this information
		/// to abort refinment if a simuliar interface has already been refined
		/// onto this module.
		/// </summary>
		private List<string> refinedReaderInterfaces = new List<string>();
		public List<string> RefinedReaderInterfaces
		{
			get { return refinedReaderInterfaces; }
		}

		/// <summary>
		/// The refined datapath component of this module
		/// </summary>
		private RefinedModel.Datapath datapath;
		public RefinedModel.Datapath Datapath
		{
			get { return datapath; }
		}

		/// <summary>
		/// The refined controller component of this module
		/// </summary>
		private RefinedModel.Controller controller;
		public RefinedModel.Controller Controller
		{
			get { return controller; }
		}

		public void RefineInterconnections(RefinedModel.Model model)
		{
			foreach (Channel channel in channels.Values)
			{
				channel.Refine(model);
			}

			foreach (Module child in children.Values)
			{
				System.Diagnostics.Trace.Assert(child.Parent == this);

				child.RefineInterconnections(model);

				RefinedModel.Datapath childDatapath = child.Datapath;

				// The following code will construct the positional binding
				// list of the child datapath 'child'. The connectors of the
				// child is examined one at a time. 

				Dictionary<int, string> positionalBindingList =
					new Dictionary<int, string>();

				foreach (IConnector connector in child.connectors.Values)
				{
					if (connector is QueueConnectorReaderInterface)
					{
						// The connector is a queue reader connector. The attached
						// queue is either a queue or another queue reader connector.
						// Find the names of the signals or ports at the present hierarchy
						// level that is bound to each of the three ports of the child

						QueueConnectorReaderInterface reader =
							connector as QueueConnectorReaderInterface;

						// parent must implement the IQueueConnectorReader interface
						IQueueConnectorReader parentReader =
							reader.AttachedQueue as IQueueConnectorReader;

						RefinedModel.InPort dataInPort  = reader.DataInPort; //childDatapath.InPorts[reader.DequeueSignalName] as RefinedModel.InPort;
						RefinedModel.InPort canReadPort = reader.CanReadPort; // childDatapath.InPorts[reader.CanReadSignalName] as RefinedModel.InPort;
						RefinedModel.OutPort doReadPort = reader.DoReadPort; //childDatapath.OutPorts[reader.DoReadSignalName] as RefinedModel.OutPort;

						positionalBindingList.Add(dataInPort.DeclarationPosition, parentReader.DequeueSignalName);
						positionalBindingList.Add(canReadPort.DeclarationPosition, parentReader.CanReadSignalName);
						positionalBindingList.Add(doReadPort.DeclarationPosition, parentReader.DoReadSignalName);
					}
					else if (connector is QueueConnectorWriterInterface)
					{
						// The connector is a queue writer connector. The attached
						// queue is either a queue or another queue writer connector.
						// Find the names of the signals or ports at the present hierarchy
						// level that is bound to each of the three ports of the child

						QueueConnectorWriterInterface writer =
							connector as QueueConnectorWriterInterface;

						Console.WriteLine(writer.EnqueueSignalName);

						// parent must implement the IQueueConnectorWriter interface
						IQueueConnectorWriter parentWriter =
							writer.AttachedQueue as IQueueConnectorWriter;

						RefinedModel.OutPort dataOutPort = writer.DataOutPort; // childDatapath.OutPorts[writer.EnqueueSignalName] as RefinedModel.OutPort;
						RefinedModel.InPort canWritePort = writer.CanWritePort; // childDatapath.InPorts[writer.CanWriteSignalName] as RefinedModel.InPort;
						RefinedModel.OutPort doWritePort = writer.DoWritePort; // childDatapath.OutPorts[writer.DoWriteSignalName] as RefinedModel.OutPort;

						positionalBindingList.Add(dataOutPort.DeclarationPosition, parentWriter.EnqueueSignalName);
						positionalBindingList.Add(canWritePort.DeclarationPosition, parentWriter.CanWriteSignalName);
						positionalBindingList.Add(doWritePort.DeclarationPosition, parentWriter.DoWriteSignalName);
					}
					else if (connector is SignalInConnector)
					{
						SignalInConnector reader =
							connector as SignalInConnector;

						ISignal parentReader =
							reader.Input as ISignal;

						RefinedModel.InPort signalPort = reader.Port;

						Console.WriteLine(signalPort == null);

						positionalBindingList.Add(signalPort.DeclarationPosition, parentReader.LocalName);
					}
					else if (connector is SignalOutConnector)
					{
						SignalOutConnector writer =
							connector as SignalOutConnector;

						ISignal parentWriter =
							writer.Output;

						positionalBindingList.Add(writer.Port.DeclarationPosition, parentWriter.LocalName);
					}
					else
					{
						throw new NotSupportedException(connector.GetType().ToString());
					}
				}

				// Convert from map to ordered list (the actual positional binding list)
				List<string> connectionNames = new List<string>();
				for (int i = 0; i < positionalBindingList.Count; i++)
				{
					if (!positionalBindingList.ContainsKey(i))
					{						
						string message =
							string.Format("No connection bound at position: " + i);

						Model.Instance.WriteHierarchyToFile("error.txt");

						throw new KeyNotFoundException(message);					
					}

					connectionNames.Add(positionalBindingList[i]);
				}

				// add to Gezel model
				this.datapath.ChildDatapathRefs.Add(childDatapath.LocalName, connectionNames.ToArray());
			}	
		}

		public void Refine(RefinedModel.Model model)
		{
			// modules should only be refined once
			if (refined)
				return;

			// mark this module has refined
			this.refined = true;

			//
			// setup the datapath
			//
			this.datapath = 
				new Weazel.Willows.RefinedModel.Datapath(model, this, uniqueName);

			foreach (SignalFlowGraph sfg in SignalFlowGraphs.Values)
				sfg.Refine(model, this.datapath);

			foreach (Register register in Registers.Values)
			{
				this.datapath.Registers.Add(register.LocalName, new RefinedModel.Register(TypeRegistry, this.datapath, register.LocalName, register.Type));
			}

			Console.WriteLine(LocalName);

			model.Datapaths.Add(this.datapath.LocalName, this.datapath);
			// 
			// setup the controller
			// 
			this.controller =
				new Weazel.Willows.RefinedModel.Controller(model, this, LocalName + "_ctrl");

			if (isHardwired)
				this.controller.Type = Weazel.Gezel.Model.Controller.ControllerType.Hardwired;
			else
				this.controller.Type = Weazel.Gezel.Model.Controller.ControllerType.Fsm;

			// set dp ref
			this.controller.DatapathRef = this.datapath.LocalName;

			// set sfg refs
			foreach (String key in signalFlowGraphs.Keys)
			{
				this.controller.SignalFlowGraphRefs.Add(key);
			}

			// set states
			foreach (String key in States.Keys)
			{
				Weazel.Gezel.Model.State gezelState = 
					new Weazel.Gezel.Model.State(this.Controller, key);

				this.controller.States.Add(key, gezelState);

				if (States[key].IsInitial)
					this.controller.InitialState = gezelState;
			}

			model.Controllers.Add(this.controller.LocalName, this.controller);

			foreach (TransitionSet set in transitionSets)
				refineTransitionSet(set);

			//
			// refine children
			//
			foreach (Module child in children.Values)
				child.Refine(model);	
		}

		private void refineTransitionSet(TransitionSet set)
		{			
			System.Diagnostics.Trace.Assert(this.datapath != null);
			System.Diagnostics.Trace.Assert(this.controller != null);

			// first, is this a wait transition?
			if (set.HasWaitTransition)
			{
				refineWaitTransition(set);
			}
			else
			{
				Weazel.Gezel.Model.Transition lastTransition = null;

				for (int i = set.Transitions.Count - 1; i >= 0; i--)
				{
					lastTransition = refineTransition(set, lastTransition, i);
				}

				this.controller.Transitions.Add(set.SourceStateName, lastTransition);
			}
		}

		private Weazel.Gezel.Model.Transition refineTransition(TransitionSet set, Weazel.Gezel.Model.Transition lastTransition, int i)
		{
			set.Transitions[i].ExecutionSet.Add("always");

			// in the given execution set which queues are being written?
			List<string> assignedIdentifiers = new List<string>();
			foreach (string sfgName in set.Transitions[i].ExecutionSet)
			{
				if (!signalFlowGraphs.ContainsKey(sfgName))
				{
					string message =
						string.Format("Cannot find sfg '{0}' in module '{1}'", sfgName, LocalName);

					throw new KeyNotFoundException(message);
				}

				SignalFlowGraph sfg = signalFlowGraphs[sfgName];

				foreach (string identifier in sfg.GetAssignedIdentifiers())
				{
					Console.WriteLine("Identifier '{0}' written in transtion from '{1}' to '{2}' (Conditional: {3})", identifier, set.SourceStateName, set.Transitions[i].TargetStateName, set.Transitions[i].IsConditional);


				}

				assignedIdentifiers.AddRange(sfg.GetAssignedIdentifiers());
			}

			List<QueueWriterInterface> writtenQueues = new List<QueueWriterInterface>();
			List<QueueWriterInterface> idleOutQueues = new List<QueueWriterInterface>();
			List<QueueReaderInterface> inQueues = new List<QueueReaderInterface>();


			foreach (ChannelReaderInterface @interface in Readers.Values)
			{
				if (@interface is QueueReaderInterface)
				{
					inQueues.Add(@interface as QueueReaderInterface);
				}
				else if (@interface is SignalReaderInterface)
				{
				}
				else
				{
					throw new NotSupportedException();
				}
			}

			foreach (ChannelWriterInterface @interface in Writers)
			{
				if (@interface is QueueWriterInterface)
				{
					QueueWriterInterface queueWriterInterface =
						@interface as QueueWriterInterface;

					if (assignedIdentifiers.Contains(queueWriterInterface.LocalName))
						writtenQueues.Add(queueWriterInterface);
					else
						idleOutQueues.Add(queueWriterInterface);
				}
				else if (@interface is SignalWriterInterface)
				{
				}
				else
				{
					throw new NotSupportedException();
				}
			}



			List<string> queuesReadyExecutionSet = new List<string>();
			List<string> queuesNotReadyExecutionSet = new List<string>();

			//queuesReadyExecutionSet.Add("always"); already included
			queuesNotReadyExecutionSet.Add("always");

			foreach (QueueWriterInterface @interface in writtenQueues)
			{
				queuesReadyExecutionSet.Add(@interface.QueueWriteSignalFlowGraphName);
				queuesNotReadyExecutionSet.Add(@interface.QueueIdleSignalFlowGraphName);
			}

			foreach (QueueWriterInterface @interface in idleOutQueues)
			{
				queuesReadyExecutionSet.Add(@interface.QueueIdleSignalFlowGraphName);
				queuesNotReadyExecutionSet.Add(@interface.QueueIdleSignalFlowGraphName);
			}

			foreach (QueueReaderInterface @interface in inQueues)
			{
				queuesReadyExecutionSet.Add(@interface.QueueIdleSignalFlowGraphName);
				queuesNotReadyExecutionSet.Add(@interface.QueueIdleSignalFlowGraphName);
			}

			foreach (string sfgName in set.Transitions[i].ExecutionSet)
				queuesReadyExecutionSet.Add(sfgName);			

			if (set.Transitions[i].IsConditional)
			{
				if (writtenQueues.Count > 0)
				{
					// Create a dummy target state to enter once the write is
					// complete. This will ensure that the queue is not written 
					// in two following cycles (which does not work!)
					Weazel.Gezel.Model.State dummyState = 
						new Weazel.Gezel.Model.State(this.controller, set.Transitions[i].TargetStateName + "_tmp");

					this.controller.States.Add(dummyState.LocalName, dummyState);

					// Create a transition from the dummy state to the actual
					// target state.
					Weazel.Gezel.Model.Transition dummy =
						new Weazel.Gezel.Model.Transition(
							this.controller, 
							set.Transitions[i].TargetStateName, 
							queuesNotReadyExecutionSet.ToArray(), 
							false, 
							dummyState.LocalName
						);

					this.controller.Transitions.Add(dummyState.LocalName, dummy);

					// Get an expression we can use to test if all written queues
					// are ready
					Weazel.Gezel.Model.Expression readyCondition =
						set.Transitions[i].Condition;

					Weazel.Gezel.Model.Expression notReadyCondition =
						set.Transitions[i].Condition;

					for (int j = 0; j < writtenQueues.Count; j++)
					{
						readyCondition = 
							writtenQueues[j].AppendCanWriteRegisterReadyToCondition(readyCondition);
						notReadyCondition = 
							writtenQueues[j].AppendCanWriteRegisterNotReadyToCondition(notReadyCondition);
					}

					// transition 1: user defined condition true and queues ready
					Weazel.Gezel.Model.Transition bothReadyTransition =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							dummyState.LocalName,
							queuesReadyExecutionSet.ToArray(),
							false,
							set.SourceStateName
						);

					// transition 2: user defined condition true and queues not ready
					Weazel.Gezel.Model.Transition queuesNotReadyTransition =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							set.SourceStateName,
							queuesNotReadyExecutionSet.ToArray(),
							false,
							set.SourceStateName
						);

					// transiton 3: 'lastTranstion' (user defined condition false)


					Weazel.Gezel.Model.Transition t =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							readyCondition,
							bothReadyTransition,
							new Weazel.Gezel.Model.Transition(
								this.controller,
								notReadyCondition,
								queuesNotReadyTransition,
								lastTransition,
								set.SourceStateName
							),
							set.SourceStateName);

					lastTransition = t;

					//lastTransition =
					//  new Weazel.Gezel.Model.Transition(
					//    this.controller,
					//    condition,
					//    readyTransition,
					//    notReadyTransition,
					//    set.SourceStateName
					//  );
				}
				else
				{
					lastTransition =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							set.Transitions[i].Condition,
							new Weazel.Gezel.Model.Transition(
								this.controller,
								set.Transitions[i].TargetStateName,
								queuesReadyExecutionSet.ToArray(),
								false,
								set.SourceStateName
							),
							lastTransition,
							set.SourceStateName
						);
				}
			}
			else
			{
				if (writtenQueues.Count > 0)
				{

					//State dummyState = 
					//	new State(this.controller, States[set.SourceStateName].IsInitial);

					Weazel.Gezel.Model.State dummyState = new Weazel.Gezel.Model.State(this.controller, set.Transitions[i].TargetStateName + "_tmp");
					
					this.controller.States.Add(dummyState.LocalName, dummyState);

					Weazel.Gezel.Model.Transition dummy = 
						new Weazel.Gezel.Model.Transition(this.controller, set.Transitions[i].TargetStateName, queuesNotReadyExecutionSet.ToArray(), false, dummyState.LocalName);

					this.controller.Transitions.Add(dummyState.LocalName, dummy);

					Weazel.Gezel.Model.Expression condition = 
						writtenQueues[0].CanWriteRegisterExpression;

					for(int j = 1; j < writtenQueues.Count; j++)
						condition = writtenQueues[j].AppendCanWriteRegisterReadyToCondition(condition);

					Weazel.Gezel.Model.Transition notReadyTransition =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							set.SourceStateName,
							queuesNotReadyExecutionSet.ToArray(),
							false,
							set.SourceStateName
						);

					Weazel.Gezel.Model.Transition readyTransition =
						new Weazel.Gezel.Model.Transition(
						this.controller,
							dummyState.LocalName,
							//set.Transitions[i].TargetStateName,
							queuesReadyExecutionSet.ToArray(),
							false,
							set.SourceStateName
					);

					lastTransition =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							condition,
							readyTransition,
							notReadyTransition,
							set.SourceStateName);

				}
				else
				{
					lastTransition =
						new Weazel.Gezel.Model.Transition(
							this.controller,
							set.Transitions[i].TargetStateName,
							queuesReadyExecutionSet.ToArray(),
							false,
							set.SourceStateName
						);
				}
			}
			return lastTransition;
		}

		private void refineWaitTransition(TransitionSet set)
		{
			// make a list of sfg's to be scheduled in case
			// the wait condition is NOT true
			List<string> doWaitSfgs = new List<string>();
			doWaitSfgs.Add("always");

			// make a list of sfg's to be scheduled in case the 
			// wait condition is true..
			List<string> doContinueSfgs = new List<string>();
			doContinueSfgs.Add("always");

			foreach (string identifier in set.WaitTransition.WaitList)
			{
				// locate the channel reader interface associated
				// with the identifier in the wait list

				foreach (ChannelReaderInterface @interface in Readers.Values)
				{
					if (@interface is QueueReaderInterface)
					{
						QueueReaderInterface reader =
							@interface as QueueReaderInterface;

						doWaitSfgs.Add(reader.QueueIdleSignalFlowGraphName);
						doContinueSfgs.Add(reader.QueueReadSignalFlowGraphName);
					}
					else if (@interface is SignalReaderInterface)
					{
						// nothing. We are not required to read in all cycles
					}
				}
			}

			foreach (ChannelReaderInterface @interface in Readers.Values)
			{
				if (@interface is QueueReaderInterface)
				{
					QueueReaderInterface queueReaderInterface =
						@interface as QueueReaderInterface;

					if (!set.WaitTransition.WaitList.Contains(queueReaderInterface.LocalName))
						doContinueSfgs.Add(queueReaderInterface.QueueIdleSignalFlowGraphName);
				}
				else if (@interface is SignalReaderInterface)
				{
					// nothing
				}
				else
				{
					throw new NotSupportedException();
				}
			}

			foreach (ChannelWriterInterface @interface in Writers)
			{
				if (@interface is QueueWriterInterface)
				{
					QueueWriterInterface queueWriterInterface =
						@interface as QueueWriterInterface;

					doWaitSfgs.Add(queueWriterInterface.QueueIdleSignalFlowGraphName);
					doContinueSfgs.Add(queueWriterInterface.QueueIdleSignalFlowGraphName);
				}
				else if (@interface is SignalWriterInterface)
				{
					SignalWriterInterface signalWriterInterface =
						@interface as SignalWriterInterface;

					doWaitSfgs.Add(signalWriterInterface.WriteZeroSignalFlowGraphName);
					doContinueSfgs.Add(signalWriterInterface.WriteZeroSignalFlowGraphName);
				}
				else
				{
					throw new NotSupportedException();
				}
			}

			Weazel.Gezel.Model.Transition doWaitTransition =
				new Weazel.Gezel.Model.Transition(
					this.controller,
					set.SourceStateName, // self loop
					doWaitSfgs.ToArray(),
					false,
					set.SourceStateName
				);

			Weazel.Gezel.Model.Transition doContinueTransition =
				new Weazel.Gezel.Model.Transition(
					this.controller,
					set.WaitTransition.TargetStateName,
					doContinueSfgs.ToArray(),
					false,
					set.SourceStateName
				);

			Weazel.Gezel.Model.Transition transition =
				new Weazel.Gezel.Model.Transition(
					this.controller,
					set.WaitTransition.GetWaitConditionExpression(this),
					doWaitTransition,
					doContinueTransition,
					set.SourceStateName
				);

			this.controller.Transitions.Add(set.SourceStateName, transition);
		}
	}
}
